import contextlib
import itertools
import os
import random
import re
import sys
import traceback
from atexit import register
from io import BytesIO
from typing import Union

import numpy as np
import paramiko
import tesserocr
from PIL import Image
from paramiko.ssh_exception import SSHException
from selenium.common.exceptions import WebDriverException, NoSuchElementException
from selenium.webdriver import Keys
from selenium.webdriver.chrome.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from sqlalchemy import select, and_, text

from base.BaseSpider import ML, BaseSpider


class PublicSpider(BaseSpider):

    def __init__(self):
        super().__init__()
        self.folder = None
        self.web = None

    def __register(self, **kwargs):
        ML.info('>>> connect sftp, sends files <<<')
        rootPath = '/root/RPA/'
        ownerPath = rootPath + self.get_settings_field('owner') + '/'
        try:
            client = paramiko.Transport((self.get_settings_field('server'), self.get_settings_field('port')))
            client.connect(username="root", password="Y8.5yzW85")
        except SSHException as e:
            ML.warning(str(e))
            return 1
        else:
            sftp = paramiko.SFTPClient.from_transport(client)
            try:
                sftp.listdir(ownerPath)
            except FileNotFoundError:
                sftp.mkdir(ownerPath)
            filenames = self.get_settings_field('logFiles')
            folder = kwargs.get('folder')

            if folder is not None and os.path.exists(folder):
                filenames.append(os.path.join(folder, os.listdir(folder)[0]))

            for file in filenames:
                with contextlib.suppress(FileNotFoundError):
                    sftp.put(localpath=file, remotepath=os.path.join(ownerPath, file.split('/')[-1]))
            print('End registration event to SFTP')

    def exit(self, status):
        if status == 1:
            ML.error('ERROR:程序将在2分钟后退出，您可以截屏当前浏览器进行到的步骤，或截取此命令窗口消息后在关闭此程序')
            register(self.__register, folder=self.folder)
            print('20s 时间停留该界面，随后将自动关闭，请勿手动强制关闭')
            self.sleep(20)
            sys.exit(status)
        super().exit(status)

    def setup(self):
        self.exec_driver_action(self.driver, 'execute_cdp_cmd', "Page.addScriptToEvaluateOnNewDocument", {
            "source": """
                    Object.defineProperty(navigator, 'webdriver', {
                    get: () => undefined
                    })
                """
        })

        self.sleep(5)

    @staticmethod
    def print_exc():
        traceback.print_exc()

    # 元素操作
    def get_element(self, obj: WebElement, parser, selector, first=True) -> Union[WebElement, list[WebElement]]:
        elements = self.exec_driver_action(obj, 'find_elements', parser, selector)
        assert elements, "element obtained by the 'get_element' method should not be empty"

        return elements[0] if first is True else elements

    def element_to_click(self, obj, parser, selector):
        elm = self.get_element(obj, parser, selector)
        self.exec_driver_action(elm, 'click')

    def send_keys_input_action_chain(self, element, value, enter=True):
        # sourcery skip: use-fstring-for-concatenation
        try:
            self.exec_driver_action(element, 'send_keys', Keys.CONTROL + 'a')
            self.exec_driver_action(element, 'send_keys', Keys.DELETE)
            self.exec_driver_action(element, 'send_keys', value)

            if enter is True:
                self.exec_driver_action(element, 'send_keys', Keys.ENTER)

        except WebDriverException as e:
            if str(e.msg).startswith('unknown error: ChromeDriver only supports characters in the BMP'):
                JS = """
                   var elm = arguments[0], txt=arguments[1];
                   elm.value += txt;
                   elm.dispatchEvent(new Event('change'));
                   """
                self.exec_driver_action(self.driver, 'execute_script', JS, element, value)
                # self.exec_set_attribute(element, 'value', value)
            else:
                raise e

    def move_to_element_view(self, element):
        return self.exec_driver_action(self.driver, 'execute_script', "arguments[0].scrollIntoView();", element)

    # 数据库操作
    def write_row(self, table, data):
        ...

    # 网页站点公用操作
    def login_website(self):
        # We need to log in before doing anything else
        pass

    def get(self, url, start_cookie: bool):
        self.exec_driver_action(self.driver, 'get', url=url)

        if not start_cookie:
            return

        if self.driver_add_cookie is False:
            print('启用cookie登录失败')
            self.exit(1)
        else:
            self.get(url, False)

    def execute_slider_validation(self, element, tracks, tracks_correct, slower=False):
        if slower is False:
            result = sum(tracks) + tracks_correct
            self.exec_driver_action_chain_nowait([
                ["drag_and_drop_by_offset", [element, result, 0]],
                ["release", [element]]
            ])
        else:
            if tracks_correct:
                tracks.append(tracks_correct)

            self.exec_driver_action_chain_nowait([["click_and_hold", [element]]])
            for x in tracks:
                self.exec_driver_action_chain_nowait([["move_by_offset", [x, 0]]])
            self.exec_driver_action_chain_nowait([["release", [element]]])

    def get_merge_images(self, img_content, location_list):
        # 图片位置混肴
        image = Image.open(img_content)
        im_list_upper = []
        im_list_done = []
        for location in location_list:
            # print(location)
            if int(location['y']) == -58:
                im_list_upper.append(image.crop((abs(int(location['x'])), 58, abs(int(location['x'])) + 10, 116)))
            if int(location['y']) == 0:
                im_list_done.append(image.crop((abs(int(location['x'])), 0, abs(int(location['x'])) + 10, 58)))

        # create new picture
        new_im = Image.new('RGB', (260, 116))
        x_offset = 0
        for i in im_list_upper:
            new_im.paste(i, (x_offset, 0))
            x_offset += 10

        x_offset = 0
        for i in im_list_done:
            new_im.paste(i, (x_offset, 58))
            x_offset += 10

        return new_im

    def download_slider_images(self, background_images_elements, filename):
        location_list = []
        image_url = ''
        for img in background_images_elements:
            image_url = re.findall(r'background-image: url\(\"(.*?)\"\); background-position: (.*?)px (.*?)px;', img.get_attribute('style'))[0][0]
            """
            location = {
                "x": re.findall(r'background-image: url\(\"(.*?)\"\); background-position: (.*?)px (.*?)px;', img.get_attribute('style'))[0][1],
                "y": re.findall(r'background-image: url\(\"(.*?)\"\); background-position: (.*?)px (.*?)px;', img.get_attribute('style'))[0][2]
            }
            print('location', location)
            """
            location_list.append(
                {
                    "x": re.findall(r'background-image: url\(\"(.*?)\"\); background-position: (.*?)px (.*?)px;', img.get_attribute('style'))[0][1],
                    "y": re.findall(r'background-image: url\(\"(.*?)\"\); background-position: (.*?)px (.*?)px;', img.get_attribute('style'))[0][2]
                }
            )

        response = self._init_request(url=image_url)
        image_content = BytesIO(response.content)

        image = self.get_merge_images(image_content, location_list)
        os.makedirs('./sliderImages/', exist_ok=True)
        image.save(f'sliderImages/{filename}.jpg')
        return image

    # 轨迹公式，也可采用其他公式
    @staticmethod
    def ease_out_quart(x):
        return 1 - pow(1 - x, 4)

    @staticmethod
    def slider_decryption(distance, seconds, ease_func):
        distance += 20
        tracks = [0]
        offsets = [0]
        for t in np.arange(0.0, seconds, 0.1):
            ease = ease_func
            offset = round(ease(t / seconds) * distance)
            tracks.append(offset - offsets[-1])
            offsets.append(offset)
        random_list = [2, 3, 4, 5, 7, 11]
        print("original track is :", tracks)
        for _ in range(3):
            v = random.choice(random_list)
            position = int((len(tracks))/v)
            tracks[position] -= 6
        print("new track is :", tracks)
        return tracks

    def get_diff_location(self, image1, image2):
        '''
        通过像素对比 找到缺口位置
        :param image1:
        :param image2:
        :return:
        '''
        for x, y in itertools.product(range(1, 259), range(1, 115)):
            if not self.is_similar(image1, image2, x, y):
                # 判断成立 表示xy这个点 两张图不一样
                return x

    def is_similar(self, image1, image2, x, y):
        pixel1 = image1.getpixel((x, y))
        pixel2 = image2.getpixel((x, y))
        print('pixel1', pixel1)
        print('pixel2', pixel2)

        return all(abs(pixel1[i]) - pixel2[i] < 50 for i in range(3))

    def get_tracks_2(self, distance, seconds, ease_func):
        """
        根据轨迹离散分布生成的数学 生成
        成功率很高 90% 往上
        :param distance: 缺口位置
        :param seconds:  时间
        :param ease_func: 生成函数
        :return: 轨迹数组
        """
        ...

    def img_file_to_text(self, filename):
        return tesserocr.file_to_text(filename)
# ----------------------------------------------------------------------------------------------------------------------


class BossSpider(PublicSpider):

    def __init__(self):
        super().__init__()
        self.conn_str = "mysql+pymysql://root:admin@localhost:3306/test_db"
        self.beensend = 1

    def check_have_company(self, company_name, office):
        res = self.conn_execute(
            text("select exists(select company_name from boss where company_name=:company_name and office=:office)"),
            data={'company_name': company_name, 'office': office}
        )

        if not res[0][0]:
            return True

        return False

    def record_company(self, data):
        for row in data:
            self.engine_execute(self.table.insert().values(**row))

    def get_company_infos(self, jobs_element):
        job_name = self.get_element(jobs_element, 'xpath', ".//span[@class='job-name']")
        salary = self.get_element(jobs_element, 'xpath', ".//span[@class='salary']")
        company_name = self.get_element(jobs_element, 'xpath', ".//h3[@class='company-name']")
        contact = self.get_element(jobs_element, 'xpath', ".//div[@class='info-public']")
        ML.info(f"岗位信息：岗位-{job_name.text}，薪资-{salary.text}，公司-{company_name.text}")

        return company_name, contact, job_name, salary

    def login_website(self):
        self.get('https://www.zhipin.com/suzhou/?ka=header-home', False)
        ML.info("boss平台登录成功")

    def search_jobs(self, keyword):
        search = self.get_element(self.driver, 'xpath', "//input[@name='query']")
        self.send_keys_input_action_chain(search, keyword)

    def for_job_list(self):
        flag = True
        retries = 3
        data = []

        while retries:
            job_list = self.into_judge(('xpath', "//li[@class='job-card-wrapper']"))
            if job_list is False:
                retries -= 1
            else:
                break

        job_list = self.get_element(self.driver, 'xpath', "//li[@class='job-card-wrapper']", False)

        for job in job_list:
            """
                job_name : 岗位名
                company_name : 公司名
                contact : 招聘者名
            """
            try:
                company_name, contact, job_name, salary = self.get_company_infos(job)
                msg = f'尊敬的{company_name.text}公司{contact.text}您好：我是廖先生，正在借助基于我开发的RPA_Spider自动化爬虫框架的”boss招呼助手“向您投递我的简历,应聘岗位是您的{job_name.text}。 我个人主攻方向是web自动化、爬虫数据采集、有一定vp能力，如果贵公司需要一名关于这些方面的技术人才可以对我进行回复，如果贵公司对于python方向是计算机视觉和web开发我可能不太合适，当然我们也可以聊聊。我目前在状态是离职，在苏州园区，全国可飞，期望的岗位是python方向（有意转市场和产品），薪资是9-15k（依据工作内容），有时间约面试，随时到岗，期待您的回复❤最后如果您对我开发的这套RPA_Spider框架感兴趣，想了解它再技术上的工程理念也可以联系我  boss助手今日已发{self.beensend}条招呼信息'

                if self.check_have_company(company_name.text, job_name.text) is False:
                    continue

                self.move_to_element_view(company_name)
                self.exec_driver_action(company_name, 'click')
                self.switch_page(wait=5)

                communicate = self.into_judge(('xpath', "(//a[contains(text(),'立即沟通')])[1]"), 5)
                if communicate is False:
                    self.close_driver_page([-1])
                    continue

                self.exec_driver_action(communicate, 'click')

                if flag is True:
                    know = self.into_judge(('xpath', "//span[text()='我知道了']"), 5)
                    if know is not False:
                        self.exec_driver_action(know, 'click')

                    flag = False

                self.sleep(random.uniform(4, 7))
                chat_input = self.get_element(self.driver, 'class name', "chat-input")
                self.send_keys_input_action_chain(chat_input, msg)

                self.sleep(1.5)
                self.close_driver_page([-1])
                data.append({'company_name': company_name.text, 'office': job_name.text, 'salary': salary.text})
                self.beensend += 1
            except Exception as e:
                print(e)
                self.print_exc()
        self.record_company(data)

    def next_page(self):
        nextButton = self.exec_driver_action(self.driver, 'find_element', 'xpath', "//div[@class='options-pages']/a[10]")
        self.exec_driver_action(nextButton, 'click')


class LagouSpider(PublicSpider):

    def __init__(self):
        super().__init__()
        self.conn_str = "mysql+pymysql://root:admin@localhost:3306/test_db"
        self.beensend = 1

    def check_have_company(self, company_name, office):
        res = self.conn_execute(
            text("select exists(select company_name from boss where company_name=:company_name and office=:office)"),
            data={'company_name': company_name, 'office': office}
        )

        if not res[0][0]:
            return True

        return False

    def record_company(self, data):
        for row in data:
            self.engine_execute(self.table.insert().values(**row))

    def get_company_infos(self, jobs_element):
        job_name = self.get_element(jobs_element, 'xpath', "./div[1]/div[1]//a")
        salary = self.get_element(jobs_element, 'xpath', ".//span[contains(@class,'money')]")
        company_name = self.get_element(jobs_element, 'xpath', "./div[1]/div[2]//a")
        ML.info(f"岗位信息：岗位-{job_name.text}，薪资-{salary.text}，公司-{company_name.text}")

        return company_name, job_name, salary

    def login_website(self):
        self.get('https://www.lagou.com/', False)
        ML.info("拉钩平台登录成功")

    def search_jobs(self, keyword):
        search = self.get_element(self.driver, 'id', "search_input")
        self.send_keys_input_action_chain(search, keyword)

    def for_job_list(self):
        flag = True
        data = []

        job_list = self.get_element(self.driver, 'xpath', "//div[@class='item__10RTO']", False)
        for job in job_list:
            """
                job_name : 岗位名
                company_name : 公司名
                contact : 招聘者名
            """
            try:
                company_name, job_name, salary = self.get_company_infos(job)

                if self.check_have_company(company_name.text, job_name.text) is False:
                    continue

                self.move_to_element_view(job_name)
                self.exec_driver_action(job_name, 'click')
                self.switch_page(wait=3)

                if flag is True:
                    self.element_to_click(self.driver, 'xpath', "//div[@class='resume-group']/li[3]/span")
                    flag = False

                self.element_to_click(self.driver, 'xpath', "//a[text()='投简历']")

                know = self.into_judge(('id', "knowed"), 5)
                if know is not False:
                    self.exec_driver_action(know, 'click')

                self.sleep(1.5)
                self.close_driver_page([-1])
                data.append({'company_name': company_name.text, 'office': job_name.text, 'salary': salary.text})
            except Exception as e:
                print(e)
                self.print_exc()
        self.record_company(data)

    def next_page(self):
        nextButton = self.exec_driver_action(self.driver, 'find_element', 'xpath', "//li[@title='下一页']")
        self.exec_driver_action(nextButton, 'click')